-
Notifications
You must be signed in to change notification settings - Fork 322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include the descriptor in keychain::Changeset
#1203
Include the descriptor in keychain::Changeset
#1203
Conversation
keychain_txout_index::Changeset
7c4fea5
to
54f7975
Compare
keychain_txout_index::Changeset
keychain::Changeset
54f7975
to
c14504e
Compare
c14504e
to
fd85943
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this forward. This is my initial round of comments!
Oh and it seems like you need to rebase. |
Hey @evanlinjin and @LLFourn, thanks a lot for your reviews. I've given some thoughts about them:
It seems to me that it would be optimal to have |
Just quoting @LLFourn's response to adding an error to
|
What do we think about using The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been trying to think of how to keep ChangeSet<K>
monotone and I think we should not change the keychain::ChangeSet and instead enforce that descriptors don't change at the bdk::wallet::ChangeSet level. This puts responsibility on the devs who who don't use bdk::wallet to keep their data consistent. They can either follow how we do it in Wallet
or go their own way if they know what they're doing. I created #1234 to demonstrate this approach, would love some feed back.
This is actually becoming my intuition too but have an idea a bit different #1234. I think the descriptor has to be in the keychain changeset because that's where it's stored in memory and it has to be there to produce new addresses. Having it elsewhere adds friction and removing that is the point of this effort. We can keep So here's my suggestion:
#[derive(Clone, Debug)]
pub struct DescriptorTxOutIndex {
inner: SpkTxOutIndex<(DescriptorId, u32)>,
// descriptors of each keychain
descriptors: BTreeMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
// last revealed indexes
last_revealed: BTreeMap<DescriptorId, u32>,
// lookahead settings for each descriptor
lookahead: BTreeMap<DescriptorId, u32>,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ChangeSet {
revealed: BTreeMap<DescriptorId, u32>
added: BTreeSet<Descriptor<DescriptorPublicKey>
} Methods in This changeset is monotone and error free and gives us most of the useful things that So here's what /// The changes made to a wallet by applying an [`Update`].
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
pub struct ChangeSet {
/// Changes to the [`LocalChain`].
///
/// [`LocalChain`]: local_chain::LocalChain
pub chain: local_chain::ChangeSet,
/// Changes to [`IndexedTxGraph`].
///
/// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph
pub indexed_tx_graph: indexed_tx_graph::ChangeSet<
ConfirmationTimeHeightAnchor,
descriptor::ChangeSet,
>,
/// Stores they keychains of the wallet and their descriptors.
/// This should only be non-empty in the first changeset of the wallet.
keychains: BTreeMap<KeychainKind, DescriptorId>.
/// Stores the network type of the wallet.
pub network: Option<Network>,
} Of course this is non-monotone but it's better to have these hacks up here than down in We'll probably want to add a Thoughts? |
So after sleeping on it I have changed my view a little bit. The design above is the most in line with the overall design philosophy and removes all possible invalid representations at the #[derive(Clone, Debug)]
pub struct KeychainTxOutIndex<K> {
inner: SpkTxOutIndex<(DescriptorId, u32)>,
// descriptors of each keychain
descriptors: BTreeMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
// last revealed indexes
last_revealed: BTreeMap<DescriptorId, u32>,
// lookahead settings for each descriptor
lookahead: BTreeMap<DescriptorId, u32>,
keychains: BTreeMap<K, DescriptorId>
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ChangeSet<K> {
revealed: BTreeMap<DescriptorId, u32>,
keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>
} This means the non-montonicity is restricted to |
@LLFourn Thanks for the detailed explanation. It helped clarify for me why persisting descriptor data at the wallet level is perhaps not enough. I think this direction is well aligned with the related effort of separating out a "header" changeset. |
|
Please rebase now that #1183 has been merged. |
@LLFourn very nice! |
I fixed some new CI issues with #1247, it just needs to be ACKd and merged and then this PR rebased. |
...secret keys in the wallet in Wallet::load
..enabled, enable it by default
We only need to loop though entries of `other`. The logic before was wasteful because we were also looping though all entries of `self` even if we do not need to modify the `self` entry.
This fixes the bug with changesets not being monotone. Previously, the result of applying changesets individually v.s. applying the aggregate of changesets may result in different `KeychainTxOutIndex` states. The nature of the changeset allows different keychain types to share the same descriptor. However, the previous design did not take this into account. To do this properly, we should keep track of all keychains currently associated with a given descriptor. However, the API only allows returning one keychain per spk/txout/outpoint (which is a good API). Therefore, we rank keychain variants by `Ord`. Earlier keychain variants have a higher rank, and the first keychain will be returned.
078a932
to
f3c463a
Compare
After a call with @evanlinjin we decided to go with his solution of using Then, either of us will open an issue in order to discuss further improvements (I don't like this solution that much and proposed one where we'd return only the latest keychain added, using a Vec instead of a set, but it still needs some discussion). At the moment, replacing descriptors is still allowed, and having multiple keychains for the same descriptor is still allowed, even when using |
f3c463a
to
9822040
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 86711d4
/// a situation where a descriptor has no associated keychain(s), and relevant [`TxOut`]s, | ||
/// [`OutPoint`]s and [`Script`]s (of that descriptor) will not be return by [`KeychainTxOutIndex`]. | ||
/// Therefore, reassigning the descriptor of a single keychain is not recommended. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 good explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK b990293
Great job everyone sticking with this one! it was a real journey.
Daniella and Evan have incorporated review comments.
keychains: BTreeMap<K, Descriptor<DescriptorPublicKey>>, | ||
inner: SpkTxOutIndex<(DescriptorId, u32)>, | ||
// keychain -> (descriptor, descriptor id) map | ||
keychains_to_descriptors: BTreeMap<K, (DescriptorId, Descriptor<DescriptorPublicKey>)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Descriptor
does not need to be here. You can just look it up in descriptor_ids_to_descriptors
.
POST merge ACK. Thanks for just one of the non-perfect options and pushing this one through. Let's see how we feel about it after using it. I left one comment that I think should be addressed. |
8dd1744 refactor(chain): compute txid once for `KeychainTxOutIndex::index_tx` (志宇) 639d735 refactor(chain): change field names to be more sane (志宇) 5a02f40 docs(chain): fix docs (志宇) c77e12b refactor(chain): `KeychainTxOutIndex` use `HashMap` for fields (志宇) 4d3846a chore(chain): s/replenish_lookahead/replenish_inner_index/ (LLFourn) 8779afd chore(chain): document insert_descriptor invariants better (LLFourn) 69f2a69 refactor(chain): improve replenish lookeahd internals (LLFourn) 5a584d0 chore(chain): Fix Indexed and KeychainIndexed documentaion (Lloyd Fournier) b8ba5a0 chore(chain): Improve documentation of keychain::ChangeSet (LLFourn) 101a09a chore(chain): Standardise KeychainTxOutIndex return types (LLFourn) bce070b chore(chain): add type IndexSpk, fix clippy type complexity warning (Steve Myers) 4d2442c chore(chain): misc docs and insert_descriptor fixes (LLFourn) bc2a8be refactor(keychain): Fix KeychainTxOutIndex range queries (LLFourn) 3b2ff0c Write failing test for keychain range querying (LLFourn) Pull request description: Fixes #1459 This reverts part of the changes in #1203. There the `SpkTxOutIndex<(K,u32)>` was changed to `SpkTxOutIndex<(DescriptorId, u32>)`. This led to a complicated translation logic in `KeychainTxOutIndex` (where the API is based on `K`) to transform calls to it to calls to the underlying `SpkTxOutIndex` (which now indexes by `DescriptorId`). The translation layer was broken when it came to translating range queries from the `KeychainTxOutIndex`. My solution was just to revert this part of the change and remove the need for a translation layer (almost) altogether. A thin translation layer remains to ensure that un-revealed spks are filtered out before being returned from the `KeychainTxOutIndex` methods. I feel like this PR could be extended to include a bunch of ergonomics improvements that are easier to implement now. But I think that's the point of #1451 so I held off and should probably go and scope creep that one instead. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### Bugfixes: * [x] This pull request breaks the existing API * [x] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: evanlinjin: ACK 8dd1744 Tree-SHA512: 283e6b6d4218902298e2e848fe847a6c85e27af4eee3e4337e3dad6eacf9beaa08ac99b1dce7b6fb199ca53931e543ea365728a81c41567a2e510cce77b12ac0
Fixes #1101
keychain/txout_index.rs
as now theChangeSet
depends on miniscriptinstead of K. The DescriptorId -> K translation is made at the
KeychainTxOutIndex level.
revealed indexes, and one for newly added keychains and their
descriptor.
Changelog notice
API changes in bdk:
impl Iterator
instead ofBTreeMap
API changes in bdk_chain:
ChangeSet
is now a struct, which includes a map for last revealedindexes, and one for keychains and descriptors.
KeychainTxOutIndex::inner
returns aSpkIterator<(DescriptorId, u32)>
KeychainTxOutIndex::outpoints
returns aBTreeSet
instead of&BTreeSet
KeychainTxOutIndex::keychains
returns aimpl Iterator
instead of&BTreeMap
KeychainTxOutIndex::txouts
doesn't return a ExactSizeIterator anymoreKeychainTxOutIndex::last_revealed_indices
returns aBTreeMap
instead of
&BTreeMap
KeychainTxOutIndex::add_keychain
has been renamed toKeychainTxOutIndex::insert_descriptor
, and now it returns a ChangeSetKeychainTxOutIndex::reveal_next_spk
returns OptionKeychainTxOutIndex::next_unused_spk
returns OptionKeychainTxOutIndex::unbounded_spk_iter
returns OptionKeychainTxOutIndex::next_index
returns OptionKeychainTxOutIndex::reveal_to_target
returns OptionKeychainTxOutIndex::revealed_keychain_spks
returns OptionKeychainTxOutIndex::unused_keychain_spks
returns OptionKeychainTxOutIndex::last_revealed_index
returns OptionKeychainTxOutIndex::keychain_outpoints
returns OptionKeychainTxOutIndex::keychain_outpoints_in_range
returns OptionKeychainTxOutIndex::last_used_index
returns None if the keychain has never been used, or if it doesn't existChecklists
All Submissions:
cargo fmt
andcargo clippy
before committingNew Features: